package main

import (
	"bufio"
	"crypto/tls"
	"fmt"
	"goCa/config"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"time"

	"gopkg.in/gomail.v2"
)

var C config.Config

// 获取到域名证书过期时间
func checkCertificateExpiry(domain string) (time.Time, error) {
	// 创建一个连接配置
	config := &tls.Config{
		InsecureSkipVerify: true, // 忽略证书验证，仅用于示例
	}

	// 连接到目标域名
	conn, err := tls.Dial("tcp", domain+":443", config)
	if err != nil {
		return time.Time{}, fmt.Errorf("failed to connect to %s: %v", domain, err)
	}
	defer conn.Close()

	// 获取连接的证书链
	state := conn.ConnectionState()
	if len(state.PeerCertificates) == 0 {
		return time.Time{}, fmt.Errorf("no certificates found for %s", domain)
	}

	// 获取第一个证书的过期时间
	expiry := state.PeerCertificates[0].NotAfter
	return expiry, nil
}

/**
 * 复制文件
 */
func copyFile(src, dst string) error {
	// 读取源文件内容
	data, err := ioutil.ReadFile(src)
	if err != nil {
		return fmt.Errorf("failed to read source file: %v", err)
	}

	// 将内容写入目标文件
	err = ioutil.WriteFile(dst, data, 0644)
	if err != nil {
		return fmt.Errorf("failed to write destination file: %v", err)
	}

	return nil
}

// 进行检测证书 过期则自动生成替换
func checkDomain(site config.SiteInfo) (flag bool) {
	expiry, err := checkCertificateExpiry(site.Domain)
	if err != nil {
		fmt.Printf("\tError: %v\n", err)
		return
	}

	// fmt.Printf("Certificate for %s expires on: %s\n", domain, expiry.Format(time.RFC1123))
	s := time.Now()
	sTiem := s.Unix()
	fmt.Println("\t域名自动检测开始:", site.Domain, s.Format("2006-01-02 15:04:05"))
	fmt.Println("\t路径：", site.Path)
	fmt.Println("\t过期时间:", expiry.Format("2006-01-02 15:04:05"))

	dateTime := time.Now().Unix()
	if site.Distance == 0 {
		site.Distance = 5
	}

	exprieTime := time.Unix(expiry.Unix()-int64(3600*24*site.Distance), 0)
	if dateTime < exprieTime.Unix() {
		fmt.Println("\t证书未过期返回，预计续签时间：", exprieTime.Format("2006-01-02"))
		// 求倒计时
		fmt.Println("\t距离证书过期还有", (expiry.Unix()-dateTime)/3600/24, "天")
		return
	}

	// 2. 执行 acme.sh 命令
	fmt.Println("\t开始执行生成证书命令")

	// 需要先删除掉 /root/.acme.sh/ 目录下的域名文件夹 否则会导致 域名key秘钥不刷新
	acmeEccPath := filepath.Join("/root/.acme.sh/", site.Domain+"_ecc")
	// 删除文件夹及其所有内容
	err = os.RemoveAll(filepath.Join(acmeEccPath))
	if err != nil {
		fmt.Printf("删除文件夹失败: %v\n", err)
		return
	}

	fmt.Println("文件夹删除成功")
	cmd := exec.Command("./acme.sh", "--issue", "-d", site.Domain, "-w", site.Path, "--force")
	cmd.Dir = C.Acme // 设置 acme.sh 脚本所在的目录

	// 获取标准输出和标准错误输出
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		fmt.Printf("\tFailed to get stdout pipe: %v\n", err)
		return
	}

	stderr, err := cmd.StderrPipe()
	if err != nil {
		fmt.Printf("\tFailed to get stderr pipe: %v\n", err)
		return
	}

	// 启动命令
	if err := cmd.Start(); err != nil {
		fmt.Printf("\tFailed to start command: %v\n", err)
		return
	}

	// 实时读取并打印标准输出
	go func() {
		scanner := bufio.NewScanner(stdout)
		for scanner.Scan() {
			fmt.Println("\t" + scanner.Text())
		}
	}()

	// 实时读取并打印标准错误输出
	go func() {
		scanner := bufio.NewScanner(stderr)
		for scanner.Scan() {
			fmt.Println("\t" + scanner.Text())
		}
	}()

	// 等待命令执行完毕
	if err := cmd.Wait(); err != nil {
		fmt.Printf("\tCommand finished with error: %v\n", err)
		return
	}

	// 3. 将生成好的文件内容写入到指定文件中
	certFile := filepath.Join(acmeEccPath, site.Domain+".cer")
	keyFile := filepath.Join(acmeEccPath, site.Domain+".key")
	fmt.Println("\t生成证书成功 进行替换证书文件内容")
	fmt.Println("\t证书文件路径：", certFile)
	fmt.Println("\t私钥文件路径：", keyFile)

	certContent, err := ioutil.ReadFile(certFile)
	if err != nil {
		fmt.Printf("\tFailed to read certificate file: %v\n", err)
		return
	}

	keyContent, err := ioutil.ReadFile(keyFile)
	if err != nil {
		fmt.Printf("\tFailed to read key file: %v\n", err)
		return
	}

	copyFile(site.Pmca, site.Pmca+time.Now().Format("2006-01-02 15:04:05")+".bak")
	err = ioutil.WriteFile(site.Pmca, certContent, 0644)
	if err != nil {
		fmt.Printf("\tFailed to write certificate file: %v\n", err)
		return
	}

	copyFile(site.Pmkey, site.Pmkey+time.Now().Format("2006-01-02 15:04:05")+".bak")
	err = ioutil.WriteFile(site.Pmkey, keyContent, 0600)
	if err != nil {
		fmt.Printf("\tFailed to write key file: %v\n", err)
		return
	}

	fmt.Println("\t证书更新成功 ", site.Domain, time.Now().Format("2006-01-02 15:04:05"))
	fmt.Println("\t耗时：", (time.Now().Unix() - sTiem), "秒")
	flag = true
	return
}

func sendEmail(email config.EmailInfo, toemails []string, content string) {
	// 创建一个新的邮件消息
	m := gomail.NewMessage()

	// 设置发件人
	m.SetHeader("From", email.From)

	// 设置收件人
	m.SetHeader("To", toemails...)

	// 设置抄送（可选）
	// m.SetHeader("Cc", "another-recipient@example.com")

	// 设置邮件主题
	m.SetHeader("Subject", email.Subject)

	// 设置邮件正文（支持HTML格式）
	m.SetBody("text/html", content)

	// 如果你需要发送附件，可以使用以下代码
	// m.Attach("/path/to/file.pdf")

	// 创建一个新的SMTP拨号器
	d := gomail.NewDialer(email.Smtp.Host, email.Smtp.Port, email.Smtp.User, email.Smtp.Pass)

	// 发送邮件
	if err := d.DialAndSend(m); err != nil {
		log.Fatalf("Could not send email: %v", err)
	}

	log.Println("Email sent successfully!")
}

func main() {

	// 创建日志文件
	logFile, err := os.Create("app.log")
	if err != nil {
		log.Fatalf("Failed to create log file: %v", err)
	}
	defer logFile.Close()
	// 设置日志输出到文件
	log.SetOutput(logFile)

	// 设置日志前缀和标志
	log.SetPrefix("APP: ")
	log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)

	log.Println("开始运行中...")
	fmt.Println("读取配置中...")
	C = *config.GetConfig()

	// fmt.Printf("%s %T", C.Toemails, C.Toemails)

	content := ""

	logStr := fmt.Sprintf("一共需要检测证书数量：%d", len(C.Sites))
	fmt.Println(logStr)
	log.Println(logStr)

	limit := 0
	for _, site := range C.Sites {
		flag := checkDomain(site)
		if flag {
			limit++
			content += fmt.Sprintf("<p>域名：%s 证书续签成功 ♫,过期时间： %s </p>", site.Domain, time.Now().AddDate(0, 3, 0).Format("2006-01-02 15:04:05"))
		}
	}

	log.Println("运行完成")
	if limit > 0 {
		sendEmail(C.Email, C.Toemails, content)
		fmt.Println("总共更新了", limit, "个证书")
		// 4. 重启Nginx服务 sudo service nginx restart
		// sudo systemctl reload nginx
		nginxCmd := exec.Command("sudo", "service", "nginx", "restart")
		nginxOutput, err := nginxCmd.CombinedOutput()
		if err != nil {
			fmt.Printf("Failed to restart Nginx: %s\n", nginxOutput)
			return
		}
		fmt.Printf("Nginx restarted successfully\n")
	}
}
